<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>PF: Example: Firewall for Home or Small Office</title>
<link rev="made" href="mailto:www@openbsd.org">
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<meta name="resource-type" content="document">
<meta name="description"   content="the OpenBSD FAQ page">
<meta name="keywords"      content="openbsd,faq,pf">
<meta name="distribution"  content="global">
</head>

<!--
Copyright (c) 2003, 2004 Joel Knight <enabled@myrealbox.com>

Permission to use, copy, modify, and distribute this documentation for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.

THE DOCUMENTATION IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS DOCUMENTATION INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS DOCUMENTATION
-->

<body bgcolor="#ffffff" text="#000000">
<!-- Passes validator.w3.org, please keep it this way;
please, use a max of 72 chars per line -->

<a href="../../index.html">
<img alt="[OpenBSD]" height=30 width=141 src="../../images/smalltitle.gif" border="0">
</a>
<p>
[<a href="carp.html">Previous: Firewall Redundancy with CARP and pfsync</a>]
[<a href="index.html">Contents</a>]

<p>
<h1><font color="#e00000">PF: Example: Firewall for Home or Small Office</font></h1>
<hr>

<h3>Table of Contents</h3>
<ul>
<li><a href="#scenario">The Scenario</a>
	<ul>
	<li><a href="#network">The Network</a>
	<li><a href="#objective">The Objective</a>
	<li><a href="#prep">Preparation</a>
	</ul>
<li><a href="#ruleset">The Ruleset</a>
	<ul>
	<li><a href="#macros">Macros</a>
	<li><a href="#options">Options</a>
	<li><a href="#scrub">Scrub</a>
	<li><a href="#nat">Network Address Translation</a>
	<li><a href="#rdr">Redirection</a>
	<li><a href="#filter">Filter Rules</a>
	</ul>
<li><a href="#allrules">The Complete Ruleset</a>
</ul>

<hr>

<a name="scenario"></a>
<h2>The Scenario</h2>
In this example, PF is running on an OpenBSD machine acting as a
firewall and NAT gateway for a small network in a home or office. The
overall objective is to provide Internet access to the network and to
allow limited access to the firewall machine from the Internet, and 
expose an internal web server to the external Internet.  This document
will go through a complete ruleset that does just that.

<a name="network"></a>
<h3>The Network</h3>
The network is setup like this:

<pre>
    
  [ COMP1 ]    [ COMP3 ]
      |            |                               
   ---+------+-----+------- xl0 [ OpenBSD ] fxp0 -------- ( Internet )
             |
         [ COMP2 ]

</pre>

<p>
There are a number of computers on the internal network; the diagram
shows three but the actual number is irrelevant.  These computers are
regular workstations used for web surfing, email, chatting, etc., except
for COMP3 which is also running a small web server.
The internal network is using the 192.168.0.0 / 255.255.255.0 network block.

<p>
The OpenBSD firewall is a Celeron 300 with two network cards: a 3com
3c905B (<tt>xl0</tt>) and an Intel EtherExpress Pro/100 (<tt>fxp0</tt>).
The firewall has
a cable connection to the Internet and is using NAT to share this
connection with the internal network. The IP address on the external
interface is dynamically assigned by the Internet Service Provider.

<a name="objective"></a>
<h3>The Objective</h3>
The objectives are:
<ul>
<li>Provide unrestricted Internet access to each internal computer.
<li>Use a "default deny" filter ruleset.
<li>Allow the following incoming traffic to the firewall from the
Internet:
	<ul>
	<li>SSH (TCP port 22): this will be used for external maintenance of
	the firewall machine.
	<li>Auth/Ident (TCP port 113): used by some services such as SMTP
	and IRC.
	<li>ICMP Echo Requests: the ICMP packet type used by
<a href="http://www.openbsd.org/cgi-bin/man.cgi?query=ping&amp;sektion=8"
>ping(8)</a>.
	</ul>
<li>Redirect TCP port 80 connection attempts (which are attempts to
access a web server) to computer COMP3.
Also, permit TCP port 80 traffic destined for COMP3 through the
firewall.
<li>Log filter statistics on the external interface.
<li>By default, reply with a TCP RST or ICMP Unreachable for blocked
packets.
<li>Make the ruleset as simple and easy to maintain as possible.
</ul>

<a name="prep"></a>
<h3>Preparation</h3>
This document assumes that the OpenBSD host has been properly configured
to act as a router, including verifying IP networking setup, Internet
connectivity, and setting the 
<a href="http://www.openbsd.org/cgi-bin/man.cgi?query=sysctl&amp;sektion=3"
>sysctl(3)</a> variables <tt>net.inet.ip.forwarding</tt> and/or 
<tt>net.inet6.ip6.forwarding</tt> to "<tt>1</tt>".
You must also have enabled PF using 
<a href="http://www.openbsd.org/cgi-bin/man.cgi?query=pfctl&amp;sektion=8&amp;manpath=OpenBSD+4.3"
>pfctl(8)</a> or by setting the appropriate variable in
<tt>/etc/rc.conf.local</tt>.

<a name="ruleset"></a>
<h2>The Ruleset</h2>
The following will step through a ruleset that will accomplish the above
goals.

<a name="macros"></a>
<h3>Macros</h3>
The following macros are defined to make maintenance and reading of
the ruleset easier:
<blockquote>
<tt>
ext_if="fxp0"<br>
int_if="xl0"<br>
<br>
tcp_services="{ 22, 113 }"<br>
icmp_types="echoreq"<br>
<br>
comp3="192.168.0.3"
</tt>
</blockquote>

<p>
The first two lines define the network interfaces that filtering will
happen on.
By definining them here, if we have to move this system to another 
machine with different hardware, we can change only those two lines, and 
the rest of the rule set will be still usable.
The third and fourth lines list the TCP port numbers of the services
that will be opened up to the Internet (SSH and ident/auth) and the ICMP
packet types that will be accepted at the firewall machine. 
Finally, the last line defines the IP address of COMP3.

<p>
<b>Note</b>: If the Internet connection required
<a href="http://www.openbsd.org/cgi-bin/man.cgi?query=pppoe&amp;sektion=8"
>PPPoE</a>, then filtering and NAT would have to take place on the
<tt>tun0</tt> interface and <i>not</i> on <tt>fxp0</tt>.

<a name="options"></a>
<h3>Options</h3>
The following two options will set the default response for
<tt>block</tt> filter rules and turn statistics logging "on" for the
external interface:
<blockquote>
<tt>
set block-policy return<br>
set loginterface $ext_if
</tt>
</blockquote>

<p>
Every Unix system has a "loopback" interface.
It's a virtual network interface that is used by applications to talk to
each other inside the system.
On OpenBSD, the loopback interface is 
<a href="http://www.openbsd.org/cgi-bin/man.cgi?query=lo&amp;sektion=4"
>lo(4)</a>.
It is considered best practice to disable all filtering on loopback
interfaces.
Using <a href="options.html#skip">set skip</a> will accomplish this.
<blockquote>
<tt>
set skip on lo
</tt>
</blockquote>
Note that we are skipping the entire interface group <tt>lo</tt>, this
way, should we later add additional loopback interfaces, we won't have
to worry about altering this portion of our existing rules file.

<a name="scrub"></a>
<h3>Scrub</h3>
There is no reason not to use the recommended scrubbing of all incoming
traffic, so this is a simple one-liner:
<blockquote>
<tt>
scrub in
</tt>
</blockquote>

<a name="nat"></a>
<h3>Network Address Translation</h3>
To perform NAT for the entire internal network the following
<tt>nat</tt> rule is used:
<blockquote>
<tt>
nat on $ext_if from !($ext_if) to any -&gt; ($ext_if)
</tt>
</blockquote>

<p>
The "<tt>!($ext_if)</tt>" could easily be replaced by a
"<tt>$int_if</tt>" in this case, but if you added multiple internal
interfaces, you would have to add additional NAT rules, whereas with
this structure, NAT will be handled on all protected interfaces.

<p>
Since the IP address on the external interface is assigned dynamically,
parenthesis are placed around the translation interface so that PF
will notice when the address changes.

<p>
As we will want to have the FTP proxy working, we'll put the NAT 
<a href="anchors.html">anchor</a> in, too:

<blockquote>
<tt>
nat-anchor "ftp-proxy/*"
</tt>
</blockquote>



<a name="rdr"></a>
<h3>Redirection</h3>
The first redirection rules needed are for 
<a href="http://www.openbsd.org/cgi-bin/man.cgi?query=ftp-proxy&amp;sektion=8&amp;manpath=OpenBSD+4.3"
>ftp-proxy(8)</a> so that FTP clients on the local network can connect
to FTP servers on the Internet.
<blockquote>
<tt>
rdr-anchor "ftp-proxy/*"<br>
rdr on $int_if proto tcp from any to any port 21 -&gt; 127.0.0.1 port 8021
</tt>
</blockquote>

<p>
Note that this rule will only catch FTP connections to port 21. If users
regularly connect to FTP servers on other ports, then a list should be
used to specify the destination port, for example: <tt>from any to any
port { 21, 2121 }</tt>.

<p>
The last redirection rule catches any attempts by someone on the
Internet to connect to TCP port 80 on the firewall. 
Legitimate attempts to access this port will be from users trying to
access the network's web server.
These connection attempts need to be redirected to COMP3:

<blockquote>
<tt>
rdr on $ext_if proto tcp from any to any port 80 -&gt; $comp3
</tt>
</blockquote>

<a name="filter"></a>
<h3>Filter Rules</h3>
Now the filter rules. Start with the default deny:
<blockquote>
<tt>
block in<br>
</tt>
</blockquote>

<p>
At this point all traffic attempting to come into an interface will
be blocked, even that from the internal network.
Later rules will open up the firewall as per
the objectives above as well as open up any necessary virtual
interfaces.

<p>
Keep in mind, pf can block traffic coming into or leaving out of an
interface.
It can simplify your life if you chose to filter traffic in one
direction, rather than trying to keep it straight when filtering some
things in, and some things out.
In our case, we'll opt to filter the inbound traffic, but once the
traffic is permitted into an interface, we won't try to obstruct it
leaving, so we will do the following:

<blockquote>
<tt>
pass out keep state
</tt>
</blockquote>

<p>
We need to have an <a href="anchors.html">anchor</a> for ftp-proxy(8):
<blockquote>
<tt>
anchor "ftp-proxy/*"
</tt>
</blockquote>

It is good to use the <a href="filter.html#antispoof">spoofed address
protection</a>:
<blockquote>
<tt>
antispoof quick for { lo $int_if }
</tt>
</blockquote>


<p>
Now open the ports used by those network services that will be available
to the Internet.
First, the traffic that is destined to the firewall itself:

<blockquote>
<tt>
pass in on $ext_if inet proto tcp from any to ($ext_if) \<br>
&nbsp;&nbsp;&nbsp;port $tcp_services flags S/SA keep state
</tt>
</blockquote>

<p>
Specifying the network ports in the macro <tt>$tcp_services</tt> makes
it simple to open additional services to the Internet by simply editing
the macro and reloading the ruleset. UDP services can also be opened up
by creating a <tt>$udp_services</tt> macro and adding a filter rule,
similar to the one above, that specifies <tt>proto udp</tt>.

<p>
In addition to having an <tt>rdr</tt> rule which passes the web server
traffic to COMP3, we MUST also pass this traffic through the firewall:
<blockquote>
<tt>
pass in on $ext_if inet proto tcp from any to $comp3 port 80 \<br>
&nbsp;&nbsp;&nbsp;&nbsp;flags S/SA synproxy state
</tt>
</blockquote>

<p>
For an added bit of safety, we'll make use of the 
<a href="filter.html#synproxy">TCP SYN Proxy</a> to further protect the
web server.

<p>
ICMP traffic needs to be passed:
<blockquote>
<tt>
pass in inet proto icmp all icmp-type $icmp_types keep state
</tt>
</blockquote>

<p>
Similar to the <tt>$tcp_services</tt> macro, the <tt>$icmp_types</tt>
macro can easily be edited to change the types of ICMP packets that will
be allowed to reach the firewall. Note that this rule applies to all
network interfaces.

<p>
Now traffic must be passed to and from the internal network.
We'll assume that the users on the internal network know what
they are doing and aren't going to be causing trouble.  This is not
necessarily a valid assumption; a much more restrictive ruleset would
be appropriate for many environments.
<blockquote>
<tt>
pass in quick on $int_if
</tt>
</blockquote>

<p>
TCP, UDP, and ICMP traffic is permitted to exit the firewall towards the
Internet due to the earlier "<tt>pass out keep state</tt>" line.
State information is kept so that the returning packets will be passed
back in through the firewall.

<a name="allrules"></a>
<h2>The Complete Ruleset</h2>
<table border=0 width="650">
<tr><td nowrap bgcolor="#EEEEEE">
<pre>
# macros
ext_if="fxp0"
int_if="xl0"

tcp_services="{ 22, 113 }"
icmp_types="echoreq"

comp3="192.168.0.3"

# options
set block-policy return
set loginterface $ext_if

set skip on lo

# scrub
scrub in

# nat/rdr
nat on $ext_if from !($ext_if) -> ($ext_if:0)
nat-anchor "ftp-proxy/*"
rdr-anchor "ftp-proxy/*"

rdr pass on $int_if proto tcp to port ftp -> 127.0.0.1 port 8021
rdr on $ext_if proto tcp from any to any port 80 -> $comp3

# filter rules
block in

pass out keep state

anchor "ftp-proxy/*"
antispoof quick for { lo $int_if }

pass in on $ext_if inet proto tcp from any to ($ext_if) \
   port $tcp_services flags S/SA keep state

pass in on $ext_if inet proto tcp from any to $comp3 port 80 \
    flags S/SA synproxy state

pass in inet proto icmp all icmp-type $icmp_types keep state

pass in quick on $int_if
</pre>
</td></tr>
</table>

<p>
[<a href="carp.html">Previous: Firewall Redundancy with CARP and pfsync</a>]
[<a href="index.html">Contents</a>]

<p>
<hr>
<a href="index.html"><img height="24" width="24" src="../../images/back.gif" border="0" alt="[back]"></a> 
<a href="mailto:www@openbsd.org">www@openbsd.org</a>
<br>
<small>$OpenBSD: example1.html,v 1.35 2008/07/27 17:13:47 nick Exp $</small>

</body>
</html> 
